// $Id: headers.c 112 2011-11-19 06:34:30Z cedric.shih@gmail.com $
/*
 * Copyright 2011 Cedric Shih (cedric dot shih at gmail dot com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <zcbuffer.h>

#include "log.h"
#include "../headers.h"
#include "headers.h"

int niohttp_headers_decoder_parse_header(
		struct niohttp_headers_decoder *decoder,
		struct zcbuffer *buffer);

struct niohttp_headers_decoder *niohttp_headers_decoder_new(void)
{
	struct niohttp_headers_decoder *codec;

	niohttp_debug("constructing");

	if (!(codec = calloc(1, sizeof(*codec)))) {
		niohttp_error("failed to calloc");
		goto exception;
	}

	return codec;

exception:
	niohttp_headers_decoder_free(codec);
	return NULL;
}

void niohttp_headers_decoder_free(struct niohttp_headers_decoder *codec)
{
	niohttp_debug("destructing: %p", codec);

	if (!(codec)) {
		return;
	}

	free(codec);
}

int niohttp_headers_decoder_reset(
		struct niohttp_headers_decoder *decoder,
		struct niohttp_headers *headers)
{
	niohttp_debug("resetting");

	decoder->headers = headers;
	decoder->state = NIOHTTP_HEADER_NAME;
//	decoder->position = position;
	decoder->offset1 = decoder->offset2 = -1;

	return 0;
}

int niohttp_headers_decoder_parse(
		struct niohttp_headers_decoder *decoder,
		struct zcbuffer *buffer, struct niohttp_headers **headers)
{
	int rc, b;

	if (zcbuffer_remaining(buffer) <= 0) {
		rc = ENODATA;
		niohttp_error("nothing to parse");
		goto finally;
	}

	while ((decoder->state != NIOHTTP_HEADERS) &&
			zcbuffer_remaining(buffer) > 0) {
		niohttp_debug("parsing at state %d: remaining=%d, position=%d",
				decoder->state, zcbuffer_remaining(buffer),
				zcbuffer_position(buffer));

		switch (decoder->state) {
		case NIOHTTP_HEADER_NAME:
			if (decoder->offset1 == -1) {
				if ((rc = zcbuffer_peek(buffer,
						zcbuffer_position(buffer),
						&b))) {
					niohttp_error("failed to peek: %s", strerror(rc));
					goto finally;
				}

				if (b == '\r') {
					niohttp_debug("hit end of headers: %d",
							zcbuffer_position(buffer));
					if ((rc = zcbuffer_skip(buffer, 1))) {
						niohttp_error("failed to skip: %s", strerror(rc));
						goto finally;
					}

					decoder->state = NIOHTTP_HEADER;
					break;
				}
			}
		case NIOHTTP_HEADER_VALUE:
		case NIOHTTP_HEADER_ENTRY:
			if ((rc = niohttp_headers_decoder_parse_header(
					decoder, buffer))) {
				niohttp_error("failed to parse header");
				goto finally;
			}
			break;
		case NIOHTTP_HEADER:
			if ((rc = zcbuffer_get(buffer, &b))) {
				niohttp_error("failed to get: %s", strerror(rc));
				goto finally;
			}

			if ((b < ' ' || b > '~') && (b != '\r' && b != '\n')) {
				niohttp_error("invalid encoding");
				rc = -1;
				goto finally;
			}

			if (b != '\n') {
				niohttp_error("unexpected end char: %d", b);
				rc = -1;
				goto finally;
			}

			niohttp_debug("hit end of meta data: %d",
					zcbuffer_position(buffer) - 1);
			decoder->state = NIOHTTP_HEADERS;
			break;
		default:
			niohttp_error("unexpected state: %d", decoder->state);
			rc = -1;
			goto finally;
		}
	}

	if (decoder->state == NIOHTTP_HEADERS) {
		*headers = decoder->headers;
	} else {
		*headers = NULL;
	}

	rc = 0;

finally:
	return rc;
}

int niohttp_headers_decoder_parse_header(
		struct niohttp_headers_decoder *decoder,
		struct zcbuffer *buffer)
{
	int rc, b;
	size_t offset;

	niohttp_trace("parsing header entry");

	while (zcbuffer_remaining(buffer) > 0) {
		if (decoder->offset1 == -1) {
			niohttp_trace("marking header name at: %d",
					zcbuffer_position(buffer));

			decoder->offset1 = zcbuffer_position(buffer);
		}

		if ((rc = zcbuffer_get(buffer, &b))) {
			niohttp_error("failed to get: %s", strerror(rc));
			goto finally;
		}

		if ((b < ' ' || b > '~') && (b != '\r' && b != '\n')) {
			niohttp_error("invalid encoding");
			rc = -1;
			goto finally;
		}

		niohttp_trace("processing: %c (0x%02x)", b, b);

		if (decoder->state == NIOHTTP_HEADER_NAME) {
			if (b == ':') {
				offset = zcbuffer_position(buffer) - 1;

				niohttp_trace("hit end of header name: %d",
						offset);

				decoder->state = NIOHTTP_HEADER_VALUE;
			}
		} else if (decoder->state == NIOHTTP_HEADER_VALUE) {
			if (decoder->offset2 == -1) {
				if (b != ' ') {
					niohttp_error("malformed header");
					rc = -1;
					goto finally;
				}

				offset = zcbuffer_position(buffer);

				niohttp_trace("marking header value at: %d",
						offset);

				decoder->offset2 = offset;
			}

			if (b == '\r') {
				offset = zcbuffer_position(buffer) - 1;

				niohttp_trace("hit end of header value: %d",
						offset);

				if ((rc = niohttp_headers_nset(
						decoder->headers,
						(char *)zcbuffer_array(buffer) + decoder->offset1,
						decoder->offset2 - 2 -
						decoder->offset1,
						(char *)zcbuffer_array(buffer) + decoder->offset2,
						offset - decoder->offset2))) {
					niohttp_error("failed to set header: %s",
							strerror(rc));
					goto finally;
				}

				decoder->state = NIOHTTP_HEADER_ENTRY;
			}
		} else if (decoder->state == NIOHTTP_HEADER_ENTRY) {
			if (b != '\n') {
				niohttp_error("unexpected end char: %d", b);
				rc = -1;
				goto finally;
			}

			offset = zcbuffer_position(buffer) - 1;

			niohttp_trace("hit end of header entry: %d", offset);

			decoder->state = NIOHTTP_HEADER_NAME;
			decoder->offset1 = decoder->offset2 = -1;
			break;
		}

		niohttp_trace("continuing");
	}

	rc = 0;

finally:
	return rc;
}

int niohttp_headers_format(struct niohttp_headers *headers,
		struct zcbuffer *buffer)
{
	int rc;
	struct niohttp_header *header;

	niohttp_debug("formatting headers to buffer");

	TAILQ_FOREACH(header, niohttp_headers_list(headers), next) {
		niohttp_debug("putting header: %s => %s",
				header->name, header->value);

		if ((rc = zcbuffer_putf(buffer, "%s: %s\r\n",
				header->name, header->value))) {
			niohttp_error("failed to put header: %s",
					strerror(rc));
			goto finally;
		}
	}

	niohttp_debug("putting end of headers");

	if ((rc = zcbuffer_putf(buffer, "%s", "\r\n"))) {
		niohttp_error("failed to put end of header: %s",
				strerror(rc));
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}
